/*
 * Created on Feb 13, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.query.engine;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.log4j.Logger;

import com.hp.hpl.jena.rdf.model.Resource;

import edu.mit.simile.longwell.Cache;
import edu.mit.simile.longwell.CascadedCache;
import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.query.ISorter;
import edu.mit.simile.longwell.query.Order;
import edu.mit.simile.longwell.query.QueryException;
import edu.mit.simile.longwell.query.Ranking;
import edu.mit.simile.longwell.query.compare.DateComparator;
import edu.mit.simile.longwell.query.compare.IComparator;
import edu.mit.simile.longwell.query.compare.IntegerComparator;
import edu.mit.simile.longwell.query.compare.StringComparator;
import edu.mit.simile.longwell.query.project.DateTimeProjector;
import edu.mit.simile.longwell.query.project.IProjection;
import edu.mit.simile.longwell.query.project.IProjector;
import edu.mit.simile.longwell.query.project.IntegerProjector;
import edu.mit.simile.longwell.query.project.PropertyProjector;
import edu.mit.simile.longwell.query.project.URIProjector;
import edu.mit.simile.longwell.schema.IStructuredProperty;

/**
 * @author dfhuynh
 */
class Sorter implements ISorter {
	final static private Logger s_logger =
		Logger.getLogger(Sorter.class);
	
	final protected Profile 		m_profile;
	final protected QueryEngine		m_engine;
	final protected Cache 			m_objectsToRankings = new Cache(10, Sorter.class, "objects-to-rankings");
	final protected CascadedCache 	m_objectsToOrderingToSorted = 
		new CascadedCache(
			Sorter.class, 
			"objects", 10,
			"ordering", 10
		);
	
	static private class MyComparator implements Comparator {
		IComparator[]	m_comparators;
		Map[]			m_objectToValueMaps;
		String[]		m_locales;
		boolean[] 		m_ascendings;

		/* (non-Javadoc)
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Object o1, Object o2) {
			Resource r1 = (Resource) o1;
			Resource r2 = (Resource) o2;
			int c = 0;
			
			for (int i = 0; c == 0 && i < m_comparators.length; i++) {
				Map map = m_objectToValueMaps[i];
				c = m_comparators[i].compare(
					map.get(r1), map.get(r2), m_locales[i], m_ascendings[i]);
			}

			if (c == 0) {
				c = r1.toString().compareTo(r2.toString());
			}
			return c;
		}
		
	}
	
	public Sorter(Profile profile, QueryEngine engine) {
		m_profile = profile;
		m_engine = engine;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.ISorter#sort(java.util.Set, java.util.List, java.lang.String, boolean)
	 */
	public List sort(Set objects, List orders, String locale, boolean fresh) throws QueryException {
		String orderString = "";
		Iterator i = orders.iterator();
		while (i.hasNext()) {
			Order order = (Order) i.next();
			
			orderString += 
				order.m_projectorName +
				order.m_projectorParameter +
				order.m_comparatorName +
				order.m_comparatorParameter +
				order.m_ascending + ";";
		}
		
		List results = (List) m_objectsToOrderingToSorted.get(objects, orderString);
		if (fresh || results == null) {
			results = internalSort(objects, orders, locale);
			m_objectsToOrderingToSorted.put(objects, orderString, results);
		}
		
		return results;
	}
	
	protected List internalSort(
			Set objects, List orders, String locale) throws QueryException {
		MyComparator c = new MyComparator();
		
		c.m_objectToValueMaps = new Map[orders.size()];
		c.m_locales = new String[orders.size()];
		c.m_comparators = new IComparator[orders.size()];
		c.m_ascendings = new boolean[orders.size()];
		
		int	i = 0;
		
		Iterator j = orders.iterator();
		while (j.hasNext()) {
			Order order = (Order) j.next();
			IProjection projection = 
				m_engine.getProjectorManager().getProjector(
						order.m_projectorName, order.m_projectorParameter, locale)
							.project(objects);
			IComparator comparator = 
				m_engine.getComparatorManager().getComparator(
						order.m_comparatorName, order.m_comparatorParameter);
			
			Map m = projection.getObjectToValueMap();
			if (m != null) {
				Map m2 = new HashMap();
				
				Iterator n = objects.iterator();
				while (n.hasNext()) {
					Object object = n.next();
					Object value = m.get(object);
					
					m2.put(object, comparator.preprocess(value, locale));
				}
				
				c.m_objectToValueMaps[i] = m2;
				c.m_locales[i] = projection.getLocale();
				c.m_comparators[i] = comparator;
				c.m_ascendings[i] = order.m_ascending;
				i++;
			}
		}
		
		TreeSet treeSet = new TreeSet(c);
		
		treeSet.addAll(objects);
		
		return new ArrayList(treeSet);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.query.ISorter#suggestOrders(java.util.Set, java.util.SortedSet, java.lang.String, boolean)
	 */
	public List suggestOrders(
			Set 		objects, 
			SortedSet 	restrictors,
			String 		locale, 
			boolean 	fresh
		) throws QueryException {
		
		List rankings = (List) m_objectsToRankings.get(objects);
		if (fresh || rankings == null) {
			rankings = internalSuggestOrders(objects, restrictors, locale);
			m_objectsToRankings.put(objects, rankings);
		}
		return rankings;
	}
	
	protected List internalSuggestOrders(
			Set objects, SortedSet restrictors, String locale) throws QueryException {
		
		if (objects.size() > 100) {
			List rankings = new ArrayList();
			
			IProjector projector = m_engine.getProjectorManager().getProjector(
					URIProjector.class.getName(), "", locale);
			
			IComparator comparator = m_engine.getComparatorManager().getComparator(
					StringComparator.class.getName(), null);
			
			rankings.add(new Ranking(
				ResourceBundle.getBundle(Sorter.class.getName()).getString("URI"),
				projector,
				comparator));
		
			return rankings;
		} else {
			SortedSet rankings = new TreeSet(new Comparator() {
				public boolean equals(Object obj) {
					return false;
				}
	
				public int compare(Object o1, Object o2) {
					ScoredRanking r1 = (ScoredRanking) o1;
					ScoredRanking r2 = (ScoredRanking) o2;
					
					int i = r1.m_score < r2.m_score ? 1 : (r1.m_score > r2.m_score ? -1 : 0);
					if (i == 0) {
						i = r1.m_label.compareToIgnoreCase(r2.m_label);
					}
					
					return i;
				}
			});
			
			Set uniqueProjectors = new HashSet();
			Iterator i = restrictors.iterator();
			while (i.hasNext()) {
				Restrictor restrictor = (Restrictor) i.next();
				
				if (restrictor.m_bucketer.matchesOneValue(restrictor.m_bucketerParameter)) {
					uniqueProjectors.add(restrictor.m_projector);
				}
			}
			
			i = m_profile.getSchemaModel().getSortedStructuredProperties(objects).iterator();
			
			outer:while (i.hasNext()) {
				try {
					Ranking ranking = 
						createRanking(
							(IStructuredProperty) i.next(), locale);
					
					IProjector projector = ranking.m_projector;
					Iterator j = uniqueProjectors.iterator();
					while (j.hasNext()) {
						IProjector projector2 = (IProjector) j.next();
						
						if (projector2.getClass().getName().equals(
								projector.getClass().getName()) &&
							projector2.getParameter().equals(
								projector.getParameter())) {
							
							continue outer;
						}
					}
					
					rankings.add(ranking);
				} catch (QueryException e) {
					s_logger.error(
						"Failed to create comparator",
						e);
				}
			}
			
			return new ArrayList(rankings);
		}
	}
	
	protected Ranking createRanking(
		IStructuredProperty sProperty, String locale) throws QueryException {
		
		String label = sProperty.getLabel(locale);
		
		String projectorName = PropertyProjector.class.getName();
		String projectorParameter = sProperty.getURI();
		String comparatorName = StringComparator.class.getName();
		
		if (sProperty.getTypeConfidence(IStructuredProperty.s_type_integer) > 0.5) {
			projectorName = IntegerProjector.class.getName();
			comparatorName = IntegerComparator.class.getName();
		//} else if (sProperty.getTypeConfidence(IStructuredProperty.s_type_numeric) > 0.5) {
		} else if (sProperty.getTypeConfidence(IStructuredProperty.s_type_dateTime) > 0.5) {
			projectorName = DateTimeProjector.class.getName();
			comparatorName = DateComparator.class.getName();
		}
		
		IProjector projector = m_engine.getProjectorManager().getProjector(
				projectorName, projectorParameter, locale);
		
		IComparator comparator = m_engine.getComparatorManager().getComparator(
				comparatorName, null);
		
		return new ScoredRanking(
			label,
			projector,
			comparator,
			projector.getUniqueness() * sProperty.countOccurrences()
		);
	}
	
	static protected class ScoredRanking extends Ranking {
		final public float m_score;
		
		/**
		 * @param label
		 * @param projector
		 * @param comparator
		 */
		public ScoredRanking(String label, IProjector projector, IComparator comparator, float score) {
			super(label, projector, comparator);
			m_score = score;
		}
	}
	
}
